package com.xshang.mybatis;

import com.xshang.tenant.TenantDataSource;
import com.xshang.tenant.TenantDataSourceContext;
import com.xshang.tenant.TenantDataSourceSwitcher;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * MyBatis配置类
 */
@Configuration
public class MyBatisConfig implements WebMvcConfigurer, ApplicationContextAware {

    @Resource
    private DefaultTenantDataSourceAutoConfig defaultTenantDataSourceAutoConfig;

    private static final String TENANT_SQL_SESSION_FACTORY = "tenantSqlSessionFactory";
    private static final String TENANT_MAPPERS_PACKAGES = "com.xshang.mapper";
    private static final String TENANT_MAPPERS_XML_LOCATIONS = "classpath:mappers/*xml";

    /**
     * 多租户数据源：一个动态数据源
     *
     * @return 多租户数据源对象
     */
    @Bean("tenantDataSource")
    public TenantDataSource tenantDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(TenantDataSourceContext.DEFAULT_TENANT_DATA_SOURCE, defaultTenantDataSourceAutoConfig.tenantDataSource());
        return new TenantDataSource(defaultTenantDataSourceAutoConfig.tenantDataSource(), targetDataSources);
    }

    /**
     * 会话工厂
     *
     * @param tenantDataSource 多租户数据源
     * @return 会话工厂对象
     * @throws IOException IO异常
     */
    @Bean(name = "tenantSqlSessionFactory")
    public SqlSessionFactoryBean tenantSqlSessionFactory(@Qualifier("tenantDataSource") DataSource tenantDataSource) throws IOException {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setMapUnderscoreToCamelCase(true);
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(tenantDataSource);
        sqlSessionFactoryBean.setConfiguration(configuration);

        // 添加拦截器
        Interceptor[] plugins = new Interceptor[]{new SqlInterceptor()};
        sqlSessionFactoryBean.setPlugins(plugins);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(TENANT_MAPPERS_XML_LOCATIONS));
        return sqlSessionFactoryBean;
    }

    /**
     * 事务管理器
     * @param tenantDataSource 多租户数据源
     * @return 事务管理器对象
     */
    @Bean
    public PlatformTransactionManager tenantTransactionManager(@Qualifier("tenantDataSource") DataSource tenantDataSource) {
        return new DataSourceTransactionManager(tenantDataSource);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
        if (autowireCapableBeanFactory instanceof DefaultListableBeanFactory) {
            scanTenantMappers(applicationContext.getEnvironment(),
                    ((DefaultListableBeanFactory) autowireCapableBeanFactory));
        }
    }

    /**
     * 扫描租户相关功能的mapper接口
     * @param env 环境
     * @param registry bean定义注册表
     */
    private void scanTenantMappers(Environment env, BeanDefinitionRegistry registry) {
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.registerFilters();
        scanner.setSqlSessionFactoryBeanName(TENANT_SQL_SESSION_FACTORY);
        scanner.setEnvironment(env);
        scanner.doScan(TENANT_MAPPERS_PACKAGES);
    }

    /**
     * 租户数据源切换器
     * @return 数据源切换器
     */
    @Bean
    public TenantDataSourceSwitcher tenantDataSourceSwitcher() {
        return new TenantDataSourceSwitcher();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截租户用户请求
        registry.addInterceptor(tenantDataSourceSwitcher()).addPathPatterns("/v1/tenant_user/**");
    }
}
